{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d877d5dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from backtrader.backtrader import AdvancedBackTrader\n",
    "from abs.DataServiceInterface import IDataService\n",
    "from sql.SQLServiceMgr import SQLServiceMgr\n",
    "from sql.utils import convert_time\n",
    "from datetime import datetime,timedelta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b187d47c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_monthly_drawdown_strategy():\n",
    "    # 初始化数据服务\n",
    "    mgr = SQLServiceMgr()\n",
    "    \n",
    "    # 设置回测参数\n",
    "    start_time = convert_time('2024-12-01 08:00:00')\n",
    "    end_time = convert_time('2025-05-01 15:00:00')\n",
    "    initial_cash = 1000000\n",
    "    fee_base = 5.0  # 最低手续费\n",
    "    fee_rate = 0.0003  # 手续费率\n",
    "    \n",
    "    # 获取所有股票代码\n",
    "    all_symbols = mgr.show_exist_symbols('min').iloc[:100]['symbol']\n",
    "    # 获取日线数据（收盘价）\n",
    "    print(\"正在获取历史日线数据...\")\n",
    "    daily_data = mgr.history(\n",
    "        symbols=all_symbols,\n",
    "        start_time=start_time,\n",
    "        end_time=end_time,\n",
    "        frequency='day',\n",
    "        fields=['close'],\n",
    "    )\n",
    "    # 转换为宽表格式：日期为索引，股票为列\n",
    "    wide_df = daily_data.pivot(index='time', columns='symbol', values='close')\n",
    "    # wide_df.fillna(method='ffill', inplace=True)  # 前向填充缺失值\n",
    "    \n",
    "    # 初始化回测引擎\n",
    "    bt = AdvancedBackTrader(\n",
    "        df=wide_df,\n",
    "        cash=initial_cash,\n",
    "        fee_base=fee_base,\n",
    "        fee_rate=fee_rate\n",
    "    )\n",
    "    \n",
    "    # 生成调仓日期序列（每月第一个交易日）\n",
    "    all_dates = wide_df.index.sort_values()\n",
    "    rebal_dates = []\n",
    "    current_month = None\n",
    "    for date in all_dates:\n",
    "        if date.month != current_month:\n",
    "            rebal_dates.append(date)\n",
    "            current_month = date.month\n",
    "    \n",
    "    print(f\"回测周期: {start_time.date()} 至 {end_time.date()}\")\n",
    "    print(f\"共 {len(rebal_dates)} 个调仓日\")\n",
    "    \n",
    "    # 按月执行调仓\n",
    "    for i in range(1, len(rebal_dates)):\n",
    "        rebal_date = rebal_dates[i]\n",
    "        prev_rebal_date = rebal_dates[i-1]\n",
    "        \n",
    "        # 计算上个月收益率\n",
    "        prev_month_data = wide_df.loc[prev_rebal_date:rebal_date - timedelta(days=1)]\n",
    "        monthly_returns = (prev_month_data.iloc[-1] - prev_month_data.iloc[0]) / prev_month_data.iloc[0]\n",
    "        \n",
    "        # 选择跌幅最大的5只股票\n",
    "        selected_symbols = monthly_returns.sort_values().head(5).index.tolist()\n",
    "        \n",
    "        # 卖出所有持仓\n",
    "        for symbol in list(bt.positions.keys()):\n",
    "            price = wide_df.loc[rebal_date, symbol]\n",
    "            if pd.isna(price):\n",
    "                price = prev_month_data[symbol].iloc[-1]  # 使用上月最后一天价格\n",
    "            bt.sell(symbol, rebal_date, price, position_pct=1.0)\n",
    "        \n",
    "        # 等权重买入新标的\n",
    "        if selected_symbols:\n",
    "            cash_per_stock = bt.cash / len(selected_symbols)\n",
    "            for symbol in selected_symbols:\n",
    "                price = wide_df.loc[rebal_date, symbol]\n",
    "                if pd.isna(price):\n",
    "                    continue\n",
    "                bt.buy(symbol, rebal_date, price, cash_pct=1/len(selected_symbols))\n",
    "        \n",
    "        print(f\"调仓日 {rebal_date}: 买入 {len(selected_symbols)} 只股票\")\n",
    "    \n",
    "    # 分析绩效并保存结果\n",
    "    report = bt.analyze_performance(\n",
    "        mgr=mgr,\n",
    "        save_to_db=True,\n",
    "        backtest_name=\"Monthly_Drawdown_Strategy\"\n",
    "    )\n",
    "    \n",
    "    print(\"\\n策略绩效报告:\")\n",
    "    for key, value in report.items():\n",
    "        print(f\"{key}: {value}\")\n",
    "\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1fc3639d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "正在获取历史日线数据...\n",
      "回测周期: 2024-12-01 至 2025-05-01\n",
      "共 5 个调仓日\n",
      "调仓日 2025-01-02: 买入 5 只股票\n",
      "调仓日 2025-02-05: 买入 5 只股票\n",
      "调仓日 2025-03-03: 买入 5 只股票\n",
      "调仓日 2025-04-01: 买入 5 只股票\n",
      "\n",
      "策略绩效报告:\n",
      "name: Monthly_Drawdown_Strategy\n",
      "start_time: 2024-12-02\n",
      "end_time: 2025-04-30\n",
      "initial_cash: 1000000\n",
      "final_value: 955173.5230400001\n",
      "total_return: -4.482647695999987\n",
      "annual_return: -10.914435910097575\n",
      "max_drawdown: 7.041902054574509\n",
      "total_fees: 898.47696\n",
      "trade_days: 4\n",
      "total_trades: 35\n",
      "sharpe_ratio: -1.1925360699785172\n",
      "sortino_ratio: -0.10425234955447549\n",
      "win_trades: 8\n",
      "loss_trades: 7\n",
      "win_rate: 22.857142857142858\n",
      "avg_win: 2771.691649999999\n",
      "avg_loss: -5513.696494285711\n",
      "profit_factor: 0.5745052531061308\n",
      "max_drawdown_start: 2025-02-07 00:00:00\n",
      "max_drawdown_end: 2025-04-07 00:00:00\n",
      "max_drawdown_duration: 59\n",
      "volatility: 11.638726830949006\n",
      "end_position_count: 5\n",
      "end_position_ratio: 65.6804219199162\n"
     ]
    }
   ],
   "source": [
    "run_monthly_drawdown_strategy()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
